<template>
  <div
    ref="imgViewport"
    class="img-viewport"
  >
    <img
      ref="img"
      :src="src"
      alt=""
      class="img"
      :style="imgStyles"
      ondragstart="return false"
      @load="handleImgLoad"
      @mousedown="startDrag"
      @mouseup="endDrag"
      @mouseleave="endDrag"
      @mousemove="drag"
      @wheel="handleWheelToZoom"
    >
    <div class="toolbar" v-if="toolbar">
      <template v-if="zoomable">
        <svg class="tool-item" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7197" width="200" height="200" @click="handleZoom(1)">
          <path d="M637 443H519V309c0-4.4-3.6-8-8-8h-60c-4.4 0-8 3.6-8 8v134H325c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h118v134c0 4.4 3.6 8 8 8h60c4.4 0 8-3.6 8-8V519h118c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z" p-id="7198" fill="#ffffff"></path><path d="M921 867L775 721c122.1-148.9 113.6-369.5-26-509-148-148.1-388.4-148.1-537 0-148.1 148.6-148.1 389 0 537 139.5 139.6 360.1 148.1 509 26l146 146c3.2 2.8 8.3 2.8 11 0l43-43c2.8-2.7 2.8-7.8 0-11zM696 696c-118.8 118.7-311.2 118.7-430 0-118.7-118.8-118.7-311.2 0-430 118.8-118.7 311.2-118.7 430 0 118.7 118.8 118.7 311.2 0 430z" p-id="7199" fill="#ffffff"></path>
        </svg>
        <svg class="tool-item" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="7412" width="200" height="200" @click="handleZoom(-1)">
          <path path d="M637 443H325c-4.4 0-8 3.6-8 8v60c0 4.4 3.6 8 8 8h312c4.4 0 8-3.6 8-8v-60c0-4.4-3.6-8-8-8z" p-id="7413" fill="#ffffff"></path><path d="M921 867L775 721c122.1-148.9 113.6-369.5-26-509-148-148.1-388.4-148.1-537 0-148.1 148.6-148.1 389 0 537 139.5 139.6 360.1 148.1 509 26l146 146c3.2 2.8 8.3 2.8 11 0l43-43c2.8-2.7 2.8-7.8 0-11zM696 696c-118.8 118.7-311.2 118.7-430 0-118.7-118.8-118.7-311.2 0-430 118.8-118.7 311.2-118.7 430 0 118.7 118.8 118.7 311.2 0 430z" p-id="7414" fill="#ffffff"></path>
        </svg>
        <svg class="tool-item" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="26672" width="200" height="200" @click="handleOriginalSize" v-if="imgWidth !== naturalWidth">
          <path d="M358.058667 128H156.970667A28.970667 28.970667 0 0 0 128 157.013333v202.837334c0 7.978667 6.528 14.506667 14.506667 14.506666h43.434666a14.506667 14.506667 0 0 0 14.506667-14.506666V200.448h157.610667a14.506667 14.506667 0 0 0 14.506666-14.506667V142.506667a14.506667 14.506667 0 0 0-14.506666-14.506667zM881.493333 649.642667h-43.434666a14.506667 14.506667 0 0 0-14.506667 14.506666v159.402667h-157.610667a14.506667 14.506667 0 0 0-14.506666 14.506667v43.434666c0 7.978667 6.570667 14.506667 14.506666 14.506667h201.088c16 0 28.970667-12.928 28.970667-29.013333v-202.837334a14.506667 14.506667 0 0 0-14.506667-14.506666zM358.058667 823.552H200.448v-159.402667a14.506667 14.506667 0 0 0-14.506667-14.506666H142.506667a14.506667 14.506667 0 0 0-14.506667 14.506666v202.88c0 16 12.970667 28.970667 29.013333 28.970667h201.045334a14.506667 14.506667 0 0 0 14.506666-14.506667v-43.434666a14.506667 14.506667 0 0 0-14.506666-14.506667zM866.986667 128h-201.088a14.506667 14.506667 0 0 0-14.506667 14.506667v43.434666c0 7.978667 6.570667 14.506667 14.506667 14.506667h157.610666v159.402667c0 7.978667 6.528 14.506667 14.506667 14.506666h43.434667a14.506667 14.506667 0 0 0 14.506666-14.506666V156.970667A28.928 28.928 0 0 0 866.986667 128z" p-id="26673" fill="#ffffff"></path>
        </svg>
        <svg class="tool-item" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="1976" width="200" height="200" @click="handleAutoSize" v-else>
          <path d="M864 128H160c-19.2 0-32 12.8-32 32v704c0 19.2 12.8 32 32 32h704c19.2 0 32-12.8 32-32V160c0-19.2-12.8-32-32-32z m-32 704H192V192h640v640z" p-id="1977" fill="#ffffff"></path><path d="M320 384v288c0 19.2 12.8 32 32 32s32-12.8 32-32V352c0-19.2-12.8-32-32-32h-32c-19.2 0-32 12.8-32 32s12.8 32 32 32zM640 384v288c0 19.2 12.8 32 32 32s32-12.8 32-32V352c0-19.2-12.8-32-32-32h-32c-19.2 0-32 12.8-32 32s12.8 32 32 32z" p-id="1978" fill="#ffffff"></path><path d="M512 384m-32 0a32 32 0 1 0 64 0 32 32 0 1 0-64 0Z" p-id="1979" fill="#ffffff"></path><path d="M512 640m-32 0a32 32 0 1 0 64 0 32 32 0 1 0-64 0Z" p-id="1980" fill="#ffffff"></path>
        </svg>
      </template>
      <template v-if="rotatable">
        <svg class="tool-item" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13308" width="200" height="200" @click="handleRotate(-1)">
          <path d="M672 418H144c-17.7 0-32 14.3-32 32v414c0 17.7 14.3 32 32 32h528c17.7 0 32-14.3 32-32V450c0-17.7-14.3-32-32-32z m-44 402H188V494h440v326z m191.3-491.5c-78.8-100.7-196-153.6-314.6-154.2l-0.2-64c0-6.5-7.6-10.1-12.6-6.1l-128 101c-4 3.1-3.9 9.1 0 12.3L492 318.6c5.1 4 12.7 0.4 12.6-6.1v-63.9c12.9 0.1 25.9 0.9 38.8 2.5 42.1 5.2 82.1 18.2 119 38.7 38.1 21.2 71.2 49.7 98.4 84.3 27.1 34.7 46.7 73.7 58.1 115.8 11 40.7 14 82.7 8.9 124.8-0.7 5.4-1.4 10.8-2.4 16.1h74.9c14.8-103.6-11.3-213-81-302.3z" p-id="13309" fill="#ffffff"></path>
        </svg>
        <svg class="tool-item" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="13521" width="200" height="200" @click="handleRotate(1)">
          <path d="M480.5 251.2c13-1.6 25.9-2.4 38.8-2.5v63.9c0 6.5 7.5 10.1 12.6 6.1L660 217.6c4-3.2 4-9.2 0-12.3l-128-101c-5.1-4-12.6-0.4-12.6 6.1l-0.2 64c-118.6 0.5-235.8 53.4-314.6 154.2-69.6 89.2-95.7 198.6-81.1 302.4h74.9c-0.9-5.3-1.7-10.7-2.4-16.1-5.1-42.1-2.1-84.1 8.9-124.8 11.4-42.2 31-81.1 58.1-115.8 27.2-34.7 60.3-63.2 98.4-84.3 37-20.6 76.9-33.6 119.1-38.8zM880 418H352c-17.7 0-32 14.3-32 32v414c0 17.7 14.3 32 32 32h528c17.7 0 32-14.3 32-32V450c0-17.7-14.3-32-32-32z m-44 402H396V494h440v326z" p-id="13522" fill="#ffffff"></path>
        </svg>
      </template>
      <template v-if="flippable">
        <svg class="tool-item" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6475" width="200" height="200" @click="handleFlip('flipX')">
          <path d="M469.333333 938.666667V85.333333c0-25.6 17.066667-42.666667 42.666667-42.666666s42.666667 17.066667 42.666667 42.666666v853.333334c0 25.6-17.066667 42.666667-42.666667 42.666666s-42.666667-17.066667-42.666667-42.666666z m170.666667-42.666667V128h170.666667c46.933333 0 85.333333 38.4 85.333333 85.333333v597.333334c0 46.933333-38.4 85.333333-85.333333 85.333333h-170.666667zM213.333333 896c-46.933333 0-85.333333-38.4-85.333333-85.333333V213.333333c0-46.933333 38.4-85.333333 85.333333-85.333333h170.666667v85.333333H234.666667c-12.8 0-21.333333 8.533333-21.333334 21.333334v554.666666c0 12.8 8.533333 21.333333 21.333334 21.333334H384v85.333333H213.333333z" p-id="6476" fill="#ffffff"></path>
        </svg>
        <svg class="tool-item" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="6701" width="200" height="200" @click="handleFlip('flipY')">
          <path d="M938.666667 554.666667H85.333333c-25.6 0-42.666667-17.066667-42.666666-42.666667s17.066667-42.666667 42.666666-42.666667h853.333334c25.6 0 42.666667 17.066667 42.666666 42.666667s-17.066667 42.666667-42.666666 42.666667z m-42.666667-170.666667H128V213.333333c0-46.933333 38.4-85.333333 85.333333-85.333333h597.333334c46.933333 0 85.333333 38.4 85.333333 85.333333v170.666667z m0 426.666667c0 46.933333-38.4 85.333333-85.333333 85.333333H213.333333c-46.933333 0-85.333333-38.4-85.333333-85.333333v-170.666667h85.333333v149.333333c0 12.8 8.533333 21.333333 21.333334 21.333334h554.666666c12.8 0 21.333333-8.533333 21.333334-21.333334V640h85.333333v170.666667z" p-id="6702" fill="#ffffff"></path>
        </svg>
      </template>
      <svg class="tool-item" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2859" width="200" height="200" @click="reset">
        <path d="M843.251 424.407l43.828-74.898c10.194-17.946 32.596-22.158 48.956-11.598 16.298 10.499 23.44 32.658 14.223 49.566l-113.11 195.212-61.164-40.044-126.418-82.285c-16.115-11.598-20.632-34.428-10.194-51.702 9.705-17.397 31.009-23.501 47.857-13.734l89.67 59.028C748.576 295.547 615.81 180.667 461.008 180.667c-177.387 0-320.042 148.758-320.042 331.335 0 183.613 143.692 331.334 319.981 331.334 107.861 0.184 208.58-56.158 268.034-149.858 1.099-1.038 1.099-2.075 2.075-2.075 6.348-9.949 17.092-15.871 28.568-15.81 19.35 0 35.648 16.848 35.648 36.93 0 7.508-2.137 14.833-6.104 21.059-72.823 114.698-196.066 183.675-328.099 183.614-216.088 0-391.4-181.478-391.4-405.195 0-223.718 175.312-405.195 391.339-405.195 183.125-0.427 342.016 131.606 382.243 317.601z" p-id="2860" fill="#ffffff"></path>
      </svg>
    </div>
  </div>
</template>

<script>
function throttle(func, wait, options) {
  let _this, args, timeout;
  let old = 0;     //时间戳
  //如果没有options就将其设置为空对象
  if (!options) options = {};

  let later = function () {
    old = new Date().valueOf();
    timeout = null;
    func.apply(_this, args);
  }
  return function () {
    _this = this;
    args = arguments;
    let now = new Date().valueOf();
    if (options.leading === false && !old) {
      old = now;
    }
    if (now - old > wait) {
      // 第一次直接执行
      if (timeout) {
        clearTimeout(timeout);
        timeout = null;
      }
      func.apply(_this, args);
      old = now;
    } else if (!timeout && options.trailing !== false) {
      //最后一次也被执行
      timeout = setTimeout(later, wait)
    }
  }
}

function debounce (func, wait, immediate) {
  let timeout;
  return function () {
    // console.log(this);  //=>从中可以测试出this指向的container
    //保存this
    let _this = this;
    // 解决前面的event指向问题
    let args = arguments;
    // 清空上从定时器
     if(timeout) clearTimeout(timeout);

    if (immediate) {
      //若执行过了,不再执行
      let callNow = !timeout;    //callNow起始值为true,timeout刚开始为undefined,则作取反操作,那么就可以保证callNow初始值为true.
      timeout = setTimeout(() => {
        timeout = null;  //在wait内,timeout是一个id数字,在在此期间callNow为false,func函数不会执行!
      }, wait);
      //初始为true会直接执行,在wait时间之内,callNow设置为timeout有值,则callNow为false,此时不执行,在wait时间之后,timeout设置为null,此时callNow为true开始执行
      if (callNow) func.apply(_this, args);

    } else {
      timeout = setTimeout(function () {
        // console.log(this)  //=>这里面的this指向window，也就是前面的count那的this是指向window
        //但是防抖函数的this应该是指向container
        func.apply(_this, args);

      }, wait)
    }
  }
}
export default {
  name: 'ImgViewer',
  props: {
    src: {
      required: true
    },
    autoZoom: {
      type: Boolean,
      default: true
    },
    toolbar: {
      type: Boolean,
      default: true
    },
    zoomable: {
      type: Boolean,
      default: true
    },
    zoomOnWheel: {
      type: Boolean,
      default: true
    },
    rotatable: {
      type: Boolean,
      default: true
    },
    flippable: {
      type: Boolean,
      default: true
    },
    movable: {
      type: Boolean,
      default: true
    },
    zoomRatio: {
      type: Number,
      default: 0.1
    }
  },
  data () {
    return {
      naturalWidth: 0,
      naturalHeight: 0,
      imgWidth: 0,
      imgHeight: 0,
      marginLeft: 0,
      marginTop: 0,
      isDragging: false,
      rotate: 0,
      flipX: 1,
      flipY: 1
    }
  },
  computed: {
    imgStyles ({ imgWidth, imgHeight, marginLeft, marginTop, isDragging, flipX, flipY, rotate }) {
      const styles = {
        width: `${imgWidth}px`,
        height: `${imgHeight}px`,
        marginLeft: `${marginLeft}px`,
        marginTop: `${marginTop}px`,
        transform: `scale(${flipX}, ${flipY}) rotate(${rotate}deg)`
      }
      if (!isDragging) {
        styles.transition = 'all 0.3s'
      }
      return styles
    }
  },
  watch: {
    imgWidth (newValue) {
      if (newValue) {
        const zoomFactor = newValue / this.naturalWidth
        this.$emit('on-zoom', zoomFactor)
      }
    }
  },
  mounted () {
    window.addEventListener('resize', this.handleResize)
  },
  beforeDestroy () {
    window.removeEventListener('resize', this.handleResize)
  },
  methods: {
    handleImgLoad () {
      this.reset()
    },
    initTransform () {
      const { imgWidth, imgHeight } = this
      const imgViewport = this.$refs.imgViewport
      const containerWidth = imgViewport.clientWidth
      const containerHeight = imgViewport.clientHeight
      const x = (containerWidth - imgWidth) / 2
      const y = (containerHeight - imgHeight) / 2
      this.marginLeft = x
      this.marginTop = y
    },
    setAutoZoom () {
      const { clientWidth: maxWidth, clientHeight: maxHeight } = this.$refs.imgViewport
      const { imgWidth, imgHeight } = this
      let scale = 1
      if (imgWidth / imgHeight > maxWidth / maxHeight) {
        scale = maxWidth / imgWidth
      } else {
        scale = maxHeight / imgHeight
      }
      this.imgWidth = imgWidth * scale
      this.imgHeight = imgHeight * scale
    },
    startDrag (event) {
      if (!this.movable) return
      this.isDragging = true
      this.startX = event.clientX - this.marginLeft
      this.startY = event.clientY - this.marginTop
    },
    drag (event) {
      if (this.isDragging) {
        this.marginLeft = event.clientX - this.startX
        this.marginTop = event.clientY - this.startY
      }
    },
    endDrag () {
      this.isDragging = false
    },
    handleWheelToZoom: debounce(function(event) {
      if (!this.zoomOnWheel) return
      const {
        clientX, // 鼠标在视窗的位置
        clientY,
        offsetX, // 鼠标在图片上的位置
        offsetY,
        deltaY
      } = event
      let scaleType = 1
      if (deltaY > 0) {
        scaleType = -1
      } else {
        scaleType = 1
      }
      const zoomFactor = 1 + this.zoomRatio // 相比于上次缩放的倍数
      const { left: imgLeft, top: imgTop } = this.$refs.img.getBoundingClientRect() // 图片与视口的距离
      // 缩放后鼠标在视口的坐标
      let diffX = offsetX * zoomFactor
      let diffY = offsetY * zoomFactor
      let afterImgWidth = this.imgWidth * zoomFactor
      let afterImgHeight = this.imgHeight * zoomFactor
      if (scaleType < 0) {
        diffX = offsetX / zoomFactor
        diffY = offsetY / zoomFactor
        afterImgWidth = this.imgWidth / zoomFactor
        afterImgHeight = this.imgHeight / zoomFactor
      }
      const afterClientX = imgLeft + diffX
      const afterClientY = imgTop + diffY
      const dx = afterClientX - clientX
      const dy = afterClientY - clientY
      this.marginLeft -= dx
      this.marginTop -= dy
      this.imgWidth = afterImgWidth
      this.imgHeight = afterImgHeight
    }, 100, true),
    reset () {
      this.position = { x: 0, y: 0 }
      this.flipX = 1
      this.flipY = 1
      this.rotate = 0
      const { naturalWidth, naturalHeight } = this.$refs.img
      this.naturalWidth = naturalWidth
      this.naturalHeight = naturalHeight
      this.imgWidth = naturalWidth
      this.imgHeight = naturalHeight
      this.autoZoom && this.setAutoZoom()
      this.initTransform()
    },
    handleZoom (scaleType) {
      const zoomFactor = 1 + this.zoomRatio
      let afterImgWidth = this.imgWidth * zoomFactor
      let afterImgHeight = this.imgHeight * zoomFactor
      if (scaleType < 0) {
        afterImgWidth = this.imgWidth / zoomFactor
        afterImgHeight = this.imgHeight / zoomFactor
      }
      this.marginLeft -= (afterImgWidth - this.imgWidth) / 2
      this.marginTop -= (afterImgHeight - this.imgHeight) / 2
      this.imgWidth = afterImgWidth
      this.imgHeight = afterImgHeight
    },
    handleFlip (direction) {
      this[direction] *= -1
    }, 
    handleRotate (direction) {
      this.rotate += direction * 90
      this.$emit('on-rotate', this.rotate)
    },
    handleOriginalSize () {
      this.imgWidth = this.naturalWidth
      this.imgHeight = this.naturalHeight
      this.initTransform()
    },
    handleAutoSize () {
      this.setAutoZoom()
      this.initTransform()
    },
    handleResize: throttle(function() {
      this.reset()
    }, 300, { leading: true })
  }
}
</script>

<style>
.img-viewport {
  width: 100%;
  height: 100%;
  overflow: hidden;
  position: relative;
}
.toolbar {
  /* width: 100%; */
  height: 40px;
  position: absolute;
  left: 50%;
  bottom: 20px;
  z-index: 1;
  border-radius: 4px;
  overflow: hidden;
  background: rgba(55,55,55,.4);
  transform: translate(-50%);
  box-sizing: border-box;
}
.tool-item {
  margin: 0 6px;
  padding: 4px 8px;
  width: 36px;
  height: 40px;
  color: #fff;
  cursor: pointer;
  transition: opacity .1s ease-in-out;
  box-sizing: border-box;
}
</style>